/*
   OpenZone2, Everquest 2 data editor
   Copyright (C) 2008  Andrew Fenn
    
	OpenZone2 is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   OpenZone2 is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with OpenZone2.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <iostream>
#include <GL/glut.h>
#include <fstream>
#include <map>
#include <vector>
#include <string>

using namespace std;

struct Edge {
	short val1;
	short val2;
};

struct Face {
	short val1;
	short val2;
	short val3;
};

struct Vector3d {
	float vert1;
	float vert2;
	float vert3;
};

struct hitfile {
	string type;
	int length;
	vector<int> vidx;
	vector<int> eidx;
	vector<int> fidx;
	vector<Vector3d*> vert;
	vector<Edge*> edge;
	vector<Face*> face;
	vector<int> leaf;
	vector<int> bran;
	vector<float> brdy;
};

hitfile mFile;
char buffer[4];
char name[1000] = {0};
char tempchar[1] = {0};
fstream binary_file;

float zoom = 15.0f;
float rotx = 0;
float roty = 0.001f;
float tx = 0;
float ty = 0;
float smallest = 9000.0f;
int lastx=0;
int lasty=0;
unsigned char Buttons[5] = {0};
int wireframe = 0;
int facecounter = 0; // run through the face data



void getFileType() {
	for (int i=0; i < 16; i++) {
			binary_file.read(tempchar,1);
			name[i] = tempchar[0];
	}
	mFile.type = string(name);
	mFile.type = mFile.type.substr(1, mFile.type.length());
}

void getHit() {
	int temp;
	binary_file.read(buffer,4);
	memcpy(&temp, buffer, 4);
	mFile.length = temp;
}

void getIDX(int type) {
	short temp;
	long length;
	vector<int> vect;
	// read how many bytes there are
	binary_file.read(buffer,4);
	memcpy(&length, buffer, 4);
	length = length/2; // half length because we're reading two bytes

	for (int i=0; i < length; i++) {
		binary_file.read(buffer,2);
		memcpy(&temp, buffer, 2);
		vect.push_back(temp);
	}
	
	if (type==1)
		mFile.vidx = vect;
	if (type==2)
		mFile.eidx = vect;
	if (type==3)
		mFile.fidx = vect;
}

void getVert() {
	long length;
	// read how many bytes there are
	binary_file.read(buffer,4);
	memcpy(&length, buffer, 4);

	length = length/(3*4); // three verts to a 3d point
	Vector3d* tempvert;
	
	for (int i=0; i < length; i++) {
		tempvert = new Vector3d;
	
		binary_file.read(buffer,4);
		memcpy(&tempvert->vert1, buffer, 4);
		
		binary_file.read(buffer,4);
		memcpy(&tempvert->vert2, buffer, 4);
	
		binary_file.read(buffer,4);
		memcpy(&tempvert->vert3, buffer, 4);
		mFile.vert.push_back(tempvert);
	}
}

void getEdge() {
	long length;
	// read how many bytes there are
	binary_file.read(buffer,4);
	memcpy(&length, buffer, 4);

	length = length/4;	
	Edge* tempEdge;
	
	for (int i=0; i < length; i++) {
		tempEdge = new Edge;
		binary_file.read(buffer,2);
		memcpy(&tempEdge->val1, buffer, 2);
		
		binary_file.read(buffer,2);
		memcpy(&tempEdge->val2, buffer, 2);
		
		mFile.edge.push_back(tempEdge);		
	}
}

void getFace() {
	unsigned short length;
	// read how many bytes there are
	binary_file.read(buffer,2);
	memcpy(&length, buffer, 2);
	
	binary_file.read(buffer,2);
	length = length/6;	
	Face* tempFace;

	for (int i=0; i < length; i++) {
		tempFace = new Face;
		
		binary_file.read(buffer,2);
		memcpy(&tempFace->val1, buffer, 2);
		
		binary_file.read(buffer,2);
		memcpy(&tempFace->val2, buffer, 2);
		
		binary_file.read(buffer,2);
		memcpy(&tempFace->val3, buffer, 2);
		
		mFile.face.push_back(tempFace);
	}
}

void getLeaf() {
	unsigned short length, temp;
	// read how many bytes there are
	binary_file.read(buffer,2);
	memcpy(&length, buffer, 2);
	
	binary_file.read(buffer,2);
	length =  length /2;

	for (int i=0; i < length; i++) {
		binary_file.read(buffer,2);
		memcpy(&temp, buffer, 2);
		mFile.leaf.push_back(temp);
	}
}

void getBran() {
	unsigned short length;
	short temp;
	// read how many bytes there are
	binary_file.read(buffer,2);
	memcpy(&length, buffer, 2);

	binary_file.read(buffer,2);

	for (int i=0; i < length; i++) {
		binary_file.read(buffer,1);
		memcpy(&temp, buffer, 1);
		mFile.bran.push_back(temp);
	}
}

void getBrdy() {
	unsigned short length;
	float temp;
	// read how many bytes there are
	binary_file.read(buffer,2);
	memcpy(&length, buffer, 2);

	binary_file.read(buffer,2);
	length = length /4;

	for (int i=0; i < length; i++) {
		binary_file.read(buffer,4);
		memcpy(&temp, buffer, 4);
		mFile.brdy.push_back(temp);
	}
}

void getType() {
	char nametype[4] = {0};
	for (int i=0; i < 4; i++) {
			binary_file.read(tempchar,1);
			nametype[i] = tempchar[0];
	}
	string foo = string(nametype);
	foo = foo.substr(0, 4);
	// extra check.. some files don't have fidx
	if (foo.compare("HIT ") == 0)
		getHit();
	if (foo.compare("VIDX") == 0)
		getIDX(1); // VIDX
	if (foo.compare("EIDX") == 0)
		getIDX(2); // EIDX
	if (foo.compare("FIDX") == 0)
		getIDX(3); // FIDX
	if (foo.compare("VERT") == 0)
		getVert();
	if (foo.compare("EDGE") == 0)
		getEdge();
	if (foo.compare("FACE") == 0)		
		getFace();
	if (foo.compare("LEAF") == 0)
		getLeaf();
	if (foo.compare("BRAN") == 0)			
		getBran();
	if (foo.compare("BDRY") == 0)		
		getBrdy();
}

/* *
   *	Start draw code
   *
   */
   

void Init() 
{
	glEnable(GL_DEPTH_TEST);
}

void draw_edge(int i, int j) {
  //  if (i>=j) return;		/* draw each edge just once */
    glVertex3f(mFile.vert[i]->vert1/10, mFile.vert[i]->vert2/10, mFile.vert[i]->vert3/10);
    //	cout << i << " " << j << endl;
    glVertex3f(mFile.vert[j]->vert1/10, mFile.vert[j]->vert2/10, mFile.vert[j]->vert3/10);
}

void display()
{

	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
 
	glLoadIdentity();
	glTranslatef(0,0,-zoom /* -400*/);
	glTranslatef(tx,ty,0);
	glRotatef(rotx,1,0,0);
	glRotatef(roty,0,1,0);	

	gluLookAt(mFile.vert[1]->vert1/10,mFile.vert[1]->vert2/10,mFile.vert[1]->vert3/10,	//	eye pos
				0,0,0,	//	aim point
				0,1,0);	//	up direction

	glColor3f(1.0f,1.0f,1.0f);
	
	
	
	int colourswitch = 2;
	int twofaces = 0; // for colouring the faces
	

	for(long faceint=1; faceint< mFile.face.size();faceint++){
		// switch colours
		if (twofaces == 20) {
			glColor3f((float)0.5*colourswitch,(float)0.5*colourswitch,(float)0.5*colourswitch);
			if (colourswitch == 2) { colourswitch = 1; } else { colourswitch = 2; }
			twofaces = 0;
		} else {
			twofaces++;
		}
		// draw grid
		if (wireframe == 0) {
			glBegin(GL_POLYGON);
			glPolygonMode(	GL_FRONT, GL_FILL);
		             
		} else {
			glBegin(GL_LINES);
			glPolygonMode(GL_FRONT, GL_LINE);
		}

		for (long i=mFile.face[faceint-1]->val1; i < mFile.face[faceint]->val1; i++) {
			draw_edge(mFile.edge[mFile.eidx[i]]->val1, mFile.edge[mFile.eidx[i]]->val2);
		}
		glEnd();
	}
	
	


	/*
	
	for(long i=0; i< mFile.eidx.size();i++){
		draw_edge(mFile.edge[mFile.eidx[i]]->val1, mFile.edge[mFile.eidx[i]]->val2);

		if (facecount < mFile.face.size() && i < mFile.face[facecount]->val1) {
			glEnd();
			if (wireframe == 0) {
				glBegin(GL_POLYGON);
				glPolygonMode(	GL_FRONT_AND_BACK, GL_FILL);
			} else {
				glBegin(GL_LINES);
				glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
			}
			// switch colours
			if (threefaces == 1) {
				glColor3f((float)0.5*colourswitch,(float)0.5*colourswitch,(float)0.5*colourswitch);
				if (colourswitch == 2) { colourswitch = 1; } else { colourswitch = 2; }
				threefaces = 0;
			} else {
				threefaces++;
			}
			facecount++;
		}
	}*/

	//glEnd();
	glutSwapBuffers();
}


void reshape(int w, int h)
{
	// prevent divide by 0 error when minimised
	if(w==0) 
		h = 1;

	glViewport(0,0,w,h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45,(float)w/h,0.1,10000);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}


void Motion(int x,int y)
{
	int diffx=x-lastx;
	int diffy=y-lasty;
	lastx=x;
	lasty=y;
	
	if( Buttons[0] && Buttons[1] )
	{
		zoom -= (float) 0.05f * diffx;
	}
	else
		if( Buttons[0] )
		{
			rotx += (float) 0.5f * diffy;
			roty += (float) 0.5f * diffx;		
		}
		else
			if( Buttons[1] )
			{
				tx += (float) 0.05f * diffx;
				ty -= (float) 0.05f * diffy;
			}
			glutPostRedisplay();
}

void Mouse(int b,int s,int x,int y)
{
	lastx=x;
	lasty=y;
	switch(b)
	{
	case GLUT_LEFT_BUTTON:
		Buttons[0] = ((GLUT_DOWN==s)?1:0);
		break;
	case GLUT_MIDDLE_BUTTON:
		Buttons[1] = ((GLUT_DOWN==s)?1:0);
		break;
	case GLUT_RIGHT_BUTTON:
		Buttons[2] = ((GLUT_DOWN==s)?1:0);
		break;
	default:
		break;		
	}
	glutPostRedisplay();
}



void processNormalKeys(unsigned char key, int x, int y) {

/* use this to get keyboard codes	
	cout << key << endl;	
	for (int i=0; i < 255; i++) {
		if (i == key)
			cout << i << endl;
	}*/
	if (key == 119) {
		if (wireframe == 1) {
			wireframe = 0;
		} else {
			wireframe = 1;
		}
	}
	
	if (key == 27) 
		exit(0);
}

int main(int argc, char** argv)
{
	if (!argv[1]) {
		cout << "Must give a .hit file" << endl;
		return 0;
	}
	// open and check the file
	binary_file.open(argv[1],ios::binary|ios::in);
	if(binary_file.fail() ) {
	   cout << "Error reading file: " << argv[1] << endl;
	   binary_file.clear();
	   return 0;
	}
	
	// Get and store the file type
	// Example: VeShaderPalette
	getFileType();
	// Check we're loading a hit file	
	if (mFile.type.compare("VeCollisionMesh") == 0) {
			binary_file.read(buffer,3); // skip three bytes specific to VecollisionMesh
			getType(); // get the file length
			if (mFile.length > 0) {
				while(binary_file.tellp() < mFile.length) {
					getType();
				}
			} else {
				cout << "Error reading file length" << endl;
				binary_file.close();
				return 0;
			}
	}

	binary_file.close();
	
	// render the stuff
	
	glutInit(&argc,argv);
	glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH);
	glutInitWindowSize(640,480);
	glutInitWindowPosition(100,100);
	
	
	string title = "EQ2 Hit file viewer - \"" + string(argv[1])+"\"";
	glutCreateWindow(title.c_str());

	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutMouseFunc(Mouse);
	glutMotionFunc(Motion);
	glutKeyboardFunc(processNormalKeys); 

	
	Init();

	glutMainLoop();
}
